﻿// ———————————————————————–
// <copyright file="CoTWrapper.cs" company="The MITRE Corporation">
//    Copyright (c) 2010 The MITRE Corporation. All rights reserved.
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
//    http://www.apache.org/licenses/LICENSE-2.0
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.
// </copyright>
// ———————————————————————–
/////////////////////////////////////////////////////////////////////////////////////////////////
// IPAWSProxy.cs - Wrapper for IPAWS to implement CAP Messaging with IPAWS
// Project: CAPLib-CAPIPAWS
//
// Language:    C#, .NET 4.0
// Platform:    Windows 7, VS 2010
// Author:      Brian Wilkins The MITRE Corporation
/////////////////////////////////////////////////////////////////////////////////////////////////

// Copyright (c) 2010 The MITRE Corporation. All rights reserved.
//
// NOTICE
// This software was produced for the U. S. Government
// under Contract No. FA8721-09-C-0001, and is
// subject to the Rights in Noncommercial Computer Software
// and Noncommercial Computer Software Documentation Clause
// (DFARS) 252.227-7014 (JUN 1995)
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Security;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
using EDXLSharp;
using EDXLSharp.EDXLDELib;
using EDXLSharp.CAPLib;
using IPAWSProxyLib.IPAWS_CAP_Svc;
using System.Runtime.Serialization.Formatters.Binary;
using log4net;
using log4net.Appender;
using log4net.Layout;
using log4net.Repository.Hierarchy;

namespace IPAWSProxyLib
{
  /// <summary>
  /// This class serves as the main connection point to the IPAWS CAP SOAP Services.
  /// </summary>
  public class CAPProxy
  {
    #region Public Delegates/Events

    #endregion

    #region Private Member Variables

    /// <summary>
    /// Default Cog User
    /// </summary>
    private const string COGUSER = "DMOPEN";

    /// <summary>
    /// Default Cog Id (for testing)
    /// </summary>
    private const int COGID = 999;

    /// <summary>
    /// Current IPAWS Request API 
    /// </summary>
    private const string REQUESTAPI = "REQUEST1";

    /// <summary>
    /// IPAWS getACK Operation Function
    /// </summary>
    private const string REQUESTPING = "getACK";

    /// <summary>
    /// IPAWS getMessageList operation
    /// </summary>
    private const string REQUESTMSGLIST = "getMessageList";

    /// <summary>
    /// IPAWS getMessage operation
    /// </summary>
    private const string REQUESTMSG = "getMessage";

    /// <summary>
    /// IPAWS client connection
    /// </summary>
    private OasisCAPserviceClient mCAPClient;
    /// <summary>
    /// IPAWS CAP header, required for all messages to IPAWS
    /// </summary>
    private CAPHeaderTypeDef mCAPHeader;

    /// <summary>
    /// Cog User for this client session
    /// </summary>
    private string mCogUser;

    /// <summary>
    /// Cog Id for this client session
    /// </summary>
    private int mCogId;

    /// <summary>
    /// Location of error folder for incorrect CAP messages
    /// </summary>
    private string mErrorFolder;

    /// <summary>
    /// Whether or not we were able to create the error folder
    /// </summary>
    private bool mErrorFolderCreationFailed;

    /// <summary>
    /// Error logger
    /// </summary>
    private static ILog log = Donuts.Core.Logging.Logger.GetLogger("Application");

    #endregion

    #region Constructors

    /// <summary>
    /// Initializes a new instance of the CAPProxy class.  Default Constructor, uses default CogId and CogUser
    /// </summary>
    public CAPProxy()
    {
      this.mErrorFolder = null;
      this.mErrorFolderCreationFailed = false;

      this.mCogId = COGID;
      this.mCogUser = COGUSER;

      try
      {
        this.InitializeClient();
      }
      catch (Exception Ex)
      {
        log.Error("Error creating CAPProxy: " + Ex.ToString());
        throw Ex;
      }

      this.CreateCAPHeader();
    }

    /// <summary>
    /// Initializes a new instance of the CAPProxy class.  Cog Constructor, uses passed in CogId and CogUser
    /// </summary>
    /// <param name="CogId">IPAWS Cog Id</param>
    /// <param name="CogUser">IPAWS Cog User</param>
    public CAPProxy(int CogId, string CogUser)
    {
      this.mErrorFolder = null;
      this.mErrorFolderCreationFailed = false;

      this.mCogId = CogId;
      this.mCogUser = CogUser;

      try
      {
        this.InitializeClient();
      }
      catch (Exception Ex)
      {
        log.Error("Error creating CAPProxy: " + Ex.ToString());
        throw Ex;
      }

      this.CreateCAPHeader();
    }

    /// <summary>
    /// Initializes a new instance of the CAPProxy class.  Cog Constructor with error file support
    /// </summary>
    /// <param name="CogId">IPAWS Cog Id</param>
    /// <param name="CogUser">IPAWS Cog User</param>
    /// <param name="ErrorFileLocation">Folder to write error files to</param>
    public CAPProxy(int CogId, string CogUser, string ErrorFileLocation)
    {
      this.mErrorFolder = ErrorFileLocation;
      this.mErrorFolderCreationFailed = false;

      this.mCogId = CogId;
      this.mCogUser = CogUser;

      try
      {
        this.InitializeClient();
      }
      catch (Exception Ex)
      {
        log.Error("Error creating CAPProxy: " + Ex.ToString());
        throw Ex;
      }

      this.CreateCAPHeader();
    }
    #endregion

    #region Public Accessors

    #endregion

    #region Public Member Functions

    /// <summary>
    /// Ping makes a "ping" request of IPAWS expects Pong in return.  
    /// This method will throw an exception if one is caught.
    /// </summary>
    public void Ping()
    {

      parameterListItem pListItem = new parameterListItem();
      pListItem.parameterName = "ping";

      parameterListItem[] pListItems = new parameterListItem[] { pListItem };

      requestParameterList request = new requestParameterList();
      request.requestAPI = REQUESTAPI;
      request.requestOperation = REQUESTPING;
      request.parameters = pListItems;
  
      string responseOperation = String.Empty;
      string responseType = String.Empty;

      try
      {
        parameterListItem1[] returnParams = mCAPClient.getRequest(mCAPHeader, request, out responseOperation, out responseType);

        if (returnParams != null)
        {
          if (returnParams[0].parameterValue.Equals("PONG"))
          {
            return;
          }
          else
          {
            log.Error("Unexpected result returned in Ping.  Expecting 'PONG'.");
            throw new System.Exception("Unexpected result returned.  Expecting 'PONG'.");
          }
        }
        else
        {
          log.Error("Unexpected result returned in Ping.  Return parameters were NULL.");

          throw new System.NullReferenceException("Unexpected result returned.  Return parameters were NULL.");
        }
      }
      catch (Exception Ex)
      {
        log.Error("Error pinging IPAWS: " + Ex.ToString());
        throw Ex;
      }
    }

    /// <summary>
    /// TryPing makes a "ping" request of IPAWS expects Pong in return.  
    /// This method will not throw an exception.
    /// </summary>
    /// <returns>Whether not Ping was successful</returns>
    public bool TryPing()
    {
      bool bGotPong = true;

      try
      {
        Ping();
      }
      catch
      {
        bGotPong = false;
      }

      return bGotPong;
    }

    /// <summary>
    /// GetMessageList returns a list of name value collections which represents the various field and values in the CAP Distribution
    /// messages that satisfy the passed in Search parameters.  Each name value collection represents a different CAP Distribution 
    /// message.  The fields include distributionId, senderId, etc... from the CAP Distribution element.  
    /// </summary>
    /// <param name="SearchName">Name of the field to search, such as distributionId.  Valid fields can be found in the 
    /// IPAWS-OPEN-v2 Interface Design Guidance 1-2 document</param>
    /// <param name="SearchValue">Value to search for, such as TestMessage01</param>
    /// <param name="ComparisonOperation">Comparison to use against the search value, such as equalto</param>
    /// <returns>List of name value collections representing the found messages</returns>
    public List<NameValueCollection> GetMessageList(string SearchName, string SearchValue, string ComparisonOperation)
    {
      parameterListItem pListItem = new parameterListItem();
      pListItem.parameterName = SearchName;
      pListItem.comparisonOp = ComparisonOperation;
      pListItem.parameterValue = new string[] { SearchValue };

      parameterListItem[] pListItems = new parameterListItem[] { pListItem };

      requestParameterList request = new requestParameterList();
      request.requestAPI = REQUESTAPI;
      request.requestOperation = REQUESTMSGLIST;
      request.parameters = pListItems;

      string responseOperation = String.Empty;
      string responseType = String.Empty;

      List<NameValueCollection> returnList;

      try
      {
        parameterListItem1[] paramList = mCAPClient.getRequest(mCAPHeader, request, out responseOperation, out responseType);

        returnList = new List<NameValueCollection>();

        if (paramList == null || paramList.Length < 1)
        {
          return returnList;
        }
        else if (paramList.Length == 1)
        {
          if (paramList[0].parameterName.Contains("NO MESSAGE"))
          {
            return returnList;
          }
        }

        foreach (parameterListItem1 param in paramList)
        {
          NameValueCollection item = new NameValueCollection();

          item.Add(param.parameterName, param.parameterValue);

          if (param.subParaListItem != null)
          {
            foreach (subParameterListItem subParam in param.subParaListItem)
            {
              item.Add(subParam.subParameterName, subParam.subParameterValue);
            }
          }
          returnList.Add(item);
        }

        return returnList;
      }
      catch (Exception Ex)
      {
        log.Error("Error getting message list: " + Ex.ToString());
        throw Ex;
      }
    }

    /// <summary>
    /// TryGetMessageList returns whether or not a GetMessages request succeeded.  
    /// </summary>
    /// <param name="SearchName">Name of the field to search, such as distributionId.  Valid fields can be found in the 
    /// IPAWS-OPEN-v2 Interface Design Guidance 1-2 document</param>
    /// <param name="SearchValue">Value to search for, such as TestMessage01</param>
    /// <param name="ComparisonOperation">Comparison to use against the search value, such as equalto</param>
    /// <param name="MessageList">A list of name value collections which represents the various field and values in the CAP Distribution
    /// messages that satisfy the passed in Search parameters.  Each name value collection represents a different CAP Distribution 
    /// message.  The fields include distributionId, senderId, etc... from the CAP Distribution element.</param>
    /// <returns>whether or not a GetMessages request succeeded</returns>
    public bool TryGetMessageList(string SearchName, string SearchValue, string ComparisonOperation, ref List<NameValueCollection> MessageList)
    {
      bool gotMessages = true;

      MessageList = null;

      try
      {
        MessageList = GetMessageList(SearchName, SearchValue, ComparisonOperation);
      }
      catch
      {
        gotMessages = false;
      }

      return gotMessages;
    }

    /// <summary>
    /// GetMessages returns a list of ICNET CAPs that satisfy the passed in search criteria
    /// </summary>
    /// <param name="SearchName">Name of the field to search, such as distributionId.  Valid fields can be found in the 
    /// IPAWS-OPEN-v2 Interface Design Guidance 1-2 document</param>
    /// <param name="SearchValue">Value to search for, such as TestMessage01</param>
    /// <param name="ComparisonOperation">Comparison to use against the search value, such as equalto</param>
    /// <returns>A list of CAP messages</returns>
    public List<CAP> GetMessages(string SearchName, string SearchValue, string ComparisonOperation)
    {
      parameterListItem pListItem = new parameterListItem();
      pListItem.parameterName = SearchName;
      pListItem.comparisonOp = ComparisonOperation;
      pListItem.parameterValue = new string[] { SearchValue };

      parameterListItem[] pListItems = new parameterListItem[] { pListItem };

      requestParameterList request = new requestParameterList();
      request.requestAPI = REQUESTAPI;
      request.requestOperation = REQUESTMSG;
      request.parameters = pListItems;

      alert[] alertArray = null;
      List<CAP> capList = null;

      try
      {
        alertArray = mCAPClient.getMessage(mCAPHeader, request);
        capList = new List<CAP>();

        if (alertArray != null && alertArray.Length > 0)
        {
          foreach (alert anAlert in alertArray)
          {
            if (!anAlert.identifier.Contains("NO MESSAGE"))
            {
              try
              {
                CAP tmp = Convert_To_ICNET_CAP(anAlert);

                if (tmp != null)
                {
                  capList.Add(tmp);
                }
              }
              catch (Exception Ex)
              {
                log.Error("Error getting messages: " + Ex.ToString());

                //if an error folder has been specified, write out error
                //otherwise ignore error and keep processing rest of list
                if (!string.IsNullOrWhiteSpace(mErrorFolder))
                {
                  WriteErrorFile(Ex, anAlert);
                }
              }
            }
          }
        }
        return capList;
      }
      catch (Exception Ex)
      {
        log.Error("Error getting messages: " + Ex.ToString());
        throw Ex;
      }
    }

    private void WriteErrorFile(Exception Ex, alert anAlert)
    {
      if (!Directory.Exists(mErrorFolder))
      {
        if (!mErrorFolderCreationFailed)
        {
          try
          {
            Directory.CreateDirectory(mErrorFolder);
          }
          catch
          {
            mErrorFolderCreationFailed = true;
          }
        }
      }
      
      if (Directory.Exists(mErrorFolder))
      {
        //use the alert's identifier if possible
        //otherwise use the current date time to the millisecond
        string fileName = (string.IsNullOrWhiteSpace(anAlert.identifier) ? DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fff") : anAlert.identifier);
        string fullFileName = Path.Combine(mErrorFolder, fileName + ".txt");

        try
        {
          using (FileStream fs = File.Create(fullFileName))
          {
            string errorMsg = "ERROR - " + Environment.NewLine + Ex.ToString() + Environment.NewLine + Environment.NewLine;

            byte[] errorBytes = System.Text.Encoding.Unicode.GetBytes(errorMsg);

            XmlSerializer alertSerializer = new XmlSerializer(typeof(alert));
            StringWriter writer = new StringWriter();
            alertSerializer.Serialize(writer, anAlert);
            StringBuilder capXML = writer.GetStringBuilder();

            byte[] alertBytes = System.Text.Encoding.Unicode.GetBytes(capXML.ToString());

            byte[] info = new byte[errorBytes.Length + alertBytes.Length];
            System.Buffer.BlockCopy(errorBytes, 0, info, 0, errorBytes.Length);
            System.Buffer.BlockCopy(alertBytes, 0, info, errorBytes.Length, alertBytes.Length);

            fs.Write(info, 0, info.Length);
          }
        }
        catch { }
      }
    }

    /// <summary>
    /// TryGetMessages returns a list of ICNET CAPs that satisfy the passed in search criteria
    /// </summary>
    /// <param name="SearchName">Name of the field to search, such as distributionId.  Valid fields can be found in the 
    /// IPAWS-OPEN-v2 Interface Design Guidance 1-2 document</param>
    /// <param name="SearchValue">Value to search for, such as TestMessage01</param>
    /// <param name="ComparisonOperation">Comparison to use against the search value, such as equalto</param>
    /// <param name="Messages">A list of CAP messages</param>
    /// <returns>Whether or not the get was successful</returns>
    public bool TryGetMessages(string SearchName, string SearchValue, string ComparisonOperation, ref List<CAP> Messages)
    {
      bool gotMessages = true;

      Messages = null;

      try
      {
        Messages = GetMessages(SearchName, SearchValue, ComparisonOperation);
      }
      catch
      {
        gotMessages = false;
      }

      return gotMessages;
    }

    /// <summary>
    /// GetMessages returns a list of ICNET CAPs that satisfy the passed in DistributionId and
    /// SenderId.  Either the DistributionId or SenderId can be null/empty/whitespace but not both.
    /// </summary>
    /// <param name="Identifier">DistributionId of the CAP message.  Can be null/empty/whitespace.</param>
    /// <param name="Sender">SenderId of the CAP message.  Can be null/empty/whitespace.</param>
    /// <returns>A list of ICNET CAPs that satisfy the passed in DistributionId and
    /// SenderId</returns>
    public List<CAP> GetMessages(string Identifier, string Sender)
    {
      if (String.IsNullOrWhiteSpace(Identifier) && String.IsNullOrWhiteSpace(Sender))
      {
        throw new System.ArgumentNullException();
      }

      parameterListItem[] pListItems = GetParameterList(Identifier, Sender);
      
      requestParameterList request = new requestParameterList();
      request.requestAPI = REQUESTAPI;
      request.requestOperation = REQUESTMSG;
      request.parameters = pListItems;

      alert[] alertArray = null;
      List<CAP> capList = null;

      try
      {
        alertArray = mCAPClient.getMessage(mCAPHeader, request);
        capList = new List<CAP>();

        if (alertArray != null && alertArray.Length > 0)
        {
          foreach (alert anAlert in alertArray)
          {
            if (!anAlert.identifier.Contains("NO MESSAGE"))
            {
              try
              {
                CAP tmp = Convert_To_ICNET_CAP(anAlert);

                if (tmp != null)
                {
                  capList.Add(tmp);
                }
              }
              catch (Exception Ex)
              {
                log.Error("Error getting messages: " + Ex.ToString());
                //if an error folder has been specified, write out error
                //otherwise ignore error and keep processing rest of list
                if (!string.IsNullOrWhiteSpace(mErrorFolder))
                {
                  WriteErrorFile(Ex, anAlert);
                }
              }
            }
          }
        }
        return capList;
      }
      catch (Exception Ex)
      {
        log.Error("Error getting messages: " + Ex.ToString());
        throw Ex;
      }
    }

    /// <summary>
    /// GetMessages returns whether or not a GetMessage request for the passed-in DistributionId and
    /// SenderId succeeded.  Either the DistributionId or SenderId can be null/empty/whitespace but not both.
    /// </summary>
    /// <param name="Identifier">DistributionId of the CAP message.  Can be null/empty/whitespace.</param>
    /// <param name="Sender">SenderId of the CAP message.  Can be null/empty/whitespace.</param>
    /// <param name="Messages">A list of ICNET CAPs that satisfy the passed in DistributionId and
    /// SenderId.</param>
    /// <returns>Whether or not a GetMessage request for the passed-in DistributionId and
    /// SenderId succeeded</returns>
    public bool TryGetMessages(string Identifier, string Sender, ref List<CAP> Messages)
    {
      bool gotMessages = true;

      Messages = null;

      try
      {
        Messages = GetMessages(Identifier, Sender);
      }
      catch
      {
        gotMessages = false;
      }

      return gotMessages;
    }

    /// <summary>
    /// PostMessage posts the provided CAP to the IPAWS server.
    /// </summary>
    /// <param name="DEObj">CAP to post to IPAWS</param>
    public bool PostMessage(CAP CAPObj)
    {
      alert ipawsAlert = null;

      try
      {
        ipawsAlert = Convert_To_IPAWS_CAP(CAPObj);
      }
      catch (Exception Ex)
      {
        log.Error("Unable to convert CAP object to alert object: " + Ex.ToString());

        // Manually flush the log. This should be folded into the logging lib in the future:
        var logger = log.Logger as Logger;
        if (logger != null)
        {
            foreach (IAppender appender in logger.Appenders)
            {
                var buffered = appender as BufferingAppenderSkeleton;
                if (buffered != null)
                {
                    buffered.Flush();
                }
            }
        }

        throw new System.ArgumentException("IPAWSProxyLib: Unable to convert CAP object to alert object: " + Ex.ToString());
      }

      try
      {
        postCAPRequestTypeDef post = new postCAPRequestTypeDef();
        post.alert = ipawsAlert;

        string postReturn = mCAPClient.postCAP(mCAPHeader, post);

        if (postReturn == null || postReturn.Contains("fail"))
        {
          return false;
        }
        else
        {
          return true;
        }
      }
      catch (Exception Ex)
      {
        log.Error("Error posting message: " + Ex.ToString());
        throw Ex;
      }
    }
    #endregion

    #region Protected Member Functions

    #endregion

    #region Private Member Functions
    /// <summary>
    /// InitializeClient creates the client connection to IPAWS, finds the appropriate IPAWS certificated based 
    /// on the CogId and CogUser used in this object's construction, and sets the client credentials to use
    /// that certificate.  Throws an error if no matching certificate can be found.
    /// </summary>
    private void InitializeClient()
    {
      bool foundCert = false;

      mCAPClient = new OasisCAPserviceClient();

      X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
      store.Open(OpenFlags.ReadOnly);

      string certSubject = "CN=" + mCogUser + "_" + mCogId.ToString();

      foreach (X509Certificate2 cert in store.Certificates)
      {
        if (cert.Subject.Contains(certSubject))
        {
          mCAPClient.ClientCredentials.ClientCertificate.Certificate = cert;
          foundCert = true;
          break;
        }
      }

      if (!foundCert)
      {
        throw new System.ArgumentException("No matching certificate found in the personal store of the current user for the provided Cog Id and User.  Id: " + mCogId + ", User: " + mCogUser);
      }
    }

    /// <summary>
    /// CreateCAPHeader creates the IPAWS CAP Header required for all messages.
    /// </summary>
    private void CreateCAPHeader()
    {
      mCAPHeader = new CAPHeaderTypeDef();
      mCAPHeader.logonUser = mCogUser;
      mCAPHeader.logonCogId = mCogId;
    }

    /// <summary>
    /// Convert_To_ICNET_CAP converts an IPAWS alert object into
    /// an ICNET CAP object.
    /// </summary>
    /// <param name="AlertObj">IPAWS alert object to transform</param>
    /// <returns>ICNET CAP object</returns>
    private CAP Convert_To_ICNET_CAP(alert AlertObj)
    {
      try
      {
        XmlSerializer alertSerializer = new XmlSerializer(typeof(alert));
        StringWriter writer = new StringWriter();
        alertSerializer.Serialize(writer, AlertObj);

        XmlSerializer capSerializer = new XmlSerializer(typeof(CAP));
        StringBuilder capXML = writer.GetStringBuilder();

        StringReader reader = new StringReader(capXML.ToString());

        //CAP temp = (CAP)capSerializer.Deserialize(reader);
        CAP temp = new CAP();
        temp.CAPv11_IPAWSProfilev10 = capXML.ToString();
        return temp;
      }
      catch (Exception Ex)
      {
        throw Ex;
      }
    }

    /// <summary>
    /// Convert_To_IPAWS_CAP converts an ICNET CAP object into
    /// an IPAWS alert object.
    /// </summary>
    /// <param name="CAPObj">ICNET CAP object to transform</param>
    /// <returns>IPAWS alert object</returns>
    private alert Convert_To_IPAWS_CAP(CAP CAPObj)
    {
      XmlSerializer s = new XmlSerializer(typeof(alert));
      StringReader reader = null;

      try
      {
        reader = new StringReader(CAPObj.CAPv11_IPAWSProfilev10);
      }
      catch( Exception Ex)
      {
        throw Ex;
      }

      alert temp = (alert)s.Deserialize(reader);

      AddDMCogAddress(temp);

      return temp;
    }

    /// <summary>
    /// AddDMCogAddress adds the required address, for the provided 
    /// CogId during construction, to the alert object if
    /// it is missing.
    /// </summary>
    /// <param name="AlertObj">alert object to add to</param>
    private void AddDMCogAddress(alert AlertObj)
    {
      //add dmcog Address as required by IPAWS
      bool cogAddressExists = false;

      if (!string.IsNullOrWhiteSpace(AlertObj.addresses))
      {
        //CAP (alert) addresses is a space delimited string
        string[] addressArray = AlertObj.addresses.Split(' ');

        foreach (string address in addressArray)
        {
          if (address.Equals(mCogId.ToString()))
          {
            cogAddressExists = true;

            break;
          }
        }
      }

      if (!cogAddressExists)
      {
        if (string.IsNullOrWhiteSpace(AlertObj.addresses))
        {
          AlertObj.addresses = mCogId.ToString();
        }
        else
        {
          AlertObj.addresses += " " + mCogId.ToString();
        }
      }
    }

    /// <summary>
    /// GetParameterList creates the appropriate parameterListItem array based
    /// on the provided DistributionId and SenderId.
    /// </summary>
    /// <param name="Identifier">DistributionId of the CAP message.  Can be null/empty/whitespace.</param>
    /// <param name="Sender">SenderId of the CAP message.  Can be null/empty/whitespace.</param>
    /// <returns>ParameterListItem array based
    /// on the provided DistributionId and SenderId</returns>
    private parameterListItem[] GetParameterList(string Identifier, string Sender)
    {
      parameterListItem identifierItem = null;
      parameterListItem senderItem = null;

      List<parameterListItem> listItems = new List<parameterListItem>();

      if (!String.IsNullOrWhiteSpace(Identifier))
      {
        identifierItem = new parameterListItem();
        identifierItem.parameterName = "identifier";
        identifierItem.comparisonOp = "equalto";
        identifierItem.parameterValue = new string[] { Identifier };

        listItems.Add(identifierItem);
      }

      if (!String.IsNullOrWhiteSpace(Sender))
      {
        senderItem = new parameterListItem();
        senderItem.parameterName = "sender";
        senderItem.comparisonOp = "equalto";
        senderItem.parameterValue = new string[] { Sender };

        //if (identifierItem != null)
        //{
        //  identifierItem.logicalOp = "and";
        //}

        listItems.Add(senderItem);
      }

      return listItems.ToArray();
    }
    #endregion
  }
}
